package org.novudu.builder;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.update.core.IFeatureReference;
import org.eclipse.update.core.ISite;
import org.eclipse.update.core.SiteManager;
import org.eclipse.update.core.VersionedIdentifier;
import org.novudu.Configuration;
import org.novudu.Distribution;
import org.novudu.Feature;
import org.novudu.NovuduFactory;
import org.novudu.Platform;
import org.novudu.Product;

public class DistributionBuilder {
	
	protected ArrayList<DistributionBuilderListener> listeners = new ArrayList<DistributionBuilderListener>();
	
	public void updateStatus(String status) {
		for (DistributionBuilderListener listener : listeners) {
			listener.updateStatus(status, progressStep);
		}
	}
	
	public void printInfo(String message) {
		for (DistributionBuilderListener listener : listeners) {
			listener.printInfo(message);
		}
	}
	
	public void printError(String message) {
		for (DistributionBuilderListener listener : listeners) {
			listener.printError(message);
		}
	}
	
	public void addListener(DistributionBuilderListener listener) {
		listeners.add(listener);
	}
	
	public void removeListener(DistributionBuilderListener listener) {
		listeners.remove(listener);
	}
	
	int progressStep = 0;
	
	public void calculateProgressStep(Configuration configuration) {
		int steps = 0;
		
		for (Product product : configuration.getProducts()) {
			if (product.isEnabled()) steps += 1; // Synchronize feature versions
		}
		
		for (Distribution distribution : configuration.getDistributions()) {
			for (Platform platform : distribution.getPlatforms()) {
				if (platform.isEnabled()) {
					steps += 1; // Find installed features
					for (Product product : distribution.getProducts()) {
						if (product.isEnabled()) {
							for (Feature feature : product.getFeatures()) {
								if (feature.isEnabled()) steps +=1; // Install feature
							}
						}
					}
				}
			}
		}
		
		if (steps > 0) {
			progressStep = (int) Math.ceil(100/steps);
			System.err.println(progressStep);
		}
		
	}
	
	public void buildAll(Configuration configuration, IProgressMonitor monitor) throws Exception {
		
		/*
		ResourceSet resourceSet = new ResourceSetImpl();
		resourceSet.getPackageRegistry().put(ConfigurationPackage.eNS_URI, ConfigurationPackage.eINSTANCE);
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl());
		Resource resource = resourceSet.createResource(URI.createFileURI(configuration));
		resource.load(null);
		*/
		
		//Configuration configuration = (Configuration) resource.getContents().get(0);
		
		calculateProgressStep(configuration);
		
		ChangeRecorder recorder = new ChangeRecorder();
		
		ArrayList<EObject> rootObjects = new ArrayList<EObject>();
		rootObjects.add(configuration);
		
		recorder.beginRecording(rootObjects);
		
		try {
		
			for (Product product : configuration.getProducts()) {
				if (!monitor.isCanceled() && product.isEnabled()) {
					if (product.isEnabled()) synchronizeFeatureVersions(product);
				}
			}
			
			for (Distribution target : configuration.getDistributions()) {
				if (target.isEnabled()) {
					for (Platform platform : target.getPlatforms()) {
						if (!monitor.isCanceled() && platform.isEnabled()) {
							findInstalledFeatures(platform);
						}
					}
				}
			}
			
			for (Distribution target : configuration.getDistributions()) {
				if (target.isEnabled()) {
					for (Platform platform : target.getPlatforms()) {
						if (platform.isEnabled()) {
							for (Product product : target.getProducts()) {
								if (!monitor.isCanceled()) {
									if (product.isEnabled()) installProduct(product, platform, monitor);
								}
							}
						}
					}
				}
			}
		}
		catch (Exception ex) {
			throw ex;
		}
		finally {
			recorder.endRecording().applyAndReverse();
		}
	
		if (monitor.isCanceled()) {
			printError("Cancelled by the user");
		}
		else {
			printInfo("Done");
		}
		
	}
	
	public List<String> findInstalledFeatures(Platform target) throws Exception {
		updateStatus("Finding installed features in platform " + target.getLocation());
		String equinoxLauncher = findEquinoxLauncher(target);
		String command = "java -jar {0} " +
				"-application  org.eclipse.update.core.standaloneUpdate " +
				"-command listFeatures";
		
		Process process = Runtime.getRuntime().exec(MessageFormat.format(command, equinoxLauncher));
		String line;
		BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
		while ((line = input.readLine()) != null) {
			if (line.trim().startsWith("Feature:")) {
				String[] parts = line.split(" ");
				Feature feature = NovuduFactory.eINSTANCE.createFeature();
				feature.setId(parts[3]);
				feature.setVersion(parts[4]);
				target.getInstalledFeatures().add(feature);
			}
		}
		input.close();
		process.waitFor();
		return null;
	}
	
	public void listFeatures(String updateSite) throws Exception {
		ISite site = SiteManager.getSite(new URL(updateSite));
		for (IFeatureReference featureReference : site.getFeatureReferences()) {
			VersionedIdentifier versionedIdentifier = featureReference.getFeature().getVersionedIdentifier();
			printInfo(versionedIdentifier.getIdentifier());
		}
	}
	
	public void synchronizeFeatureVersions(Product product) throws Exception {
		updateStatus("Synchronizing feature versions for product " + product.getName());
		ISite site = SiteManager.getSite(new URL(product.getUpdatesite()));
		for (Feature feature : product.getFeatures()) {
			for (IFeatureReference featureReference : site.getFeatureReferences()) {
				VersionedIdentifier versionedIdentifier = featureReference.getFeature().getVersionedIdentifier();
				if (versionedIdentifier.getIdentifier().equals(feature.getId()) && "latest".equals(feature.getVersion())) {
					feature.setVersion(versionedIdentifier.getVersion().toString());
				}
			}
		}
	}
	
	public void disableFeature(Feature feature, Platform target) throws Exception {
		String command = "java -jar {0} " +
		"-application  org.eclipse.update.core.standaloneUpdate " +
		"-command disable " + 
		"-featureId {1} " + 
		"-version {2} ";
		
		String equinoxLauncher = findEquinoxLauncher(target);
		
		Process process = Runtime.getRuntime().exec(MessageFormat.format(command, equinoxLauncher, feature.getId(), feature.getVersion()));
		String line;
		BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
		while ((line = input.readLine()) != null) {
			printInfo(line);
		}
		input.close();
		process.waitFor();
	}
	
	public void uninstallFeature(Feature feature, Platform target) throws Exception {
		
		printInfo("Disabling feature " + feature.getId() + "...");
		disableFeature(feature, target);
		
		printInfo("Removing feature " + feature.getId() + "...");
		
		String command = "java -jar {0} " +
		"-application  org.eclipse.update.core.standaloneUpdate " +
		"-command uninstall " + 
		"-featureId {1} " + 
		"-version {2} ";
		
		String equinoxLauncher = findEquinoxLauncher(target);
		
		Process process = Runtime.getRuntime().exec(MessageFormat.format(command, equinoxLauncher, feature.getId(), feature.getVersion()));
		String line;
		BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
		while ((line = input.readLine()) != null) {
			printInfo(line);
		}
		input.close();
		process.waitFor();
	}
	
	public void installProduct(Product product, Platform target, IProgressMonitor monitor) throws Exception {
		
		String command = "java -jar {0} " +
				"-application  org.eclipse.update.core.standaloneUpdate " +
				"-command install " + 
				"-featureId {1} " + 
				"-version {2} " +
				"-from {3}";
		String equinoxLauncher = findEquinoxLauncher(target);
		for (Feature feature : product.getFeatures()) {
			
			if (monitor.isCanceled()) return;
			if (!feature.isEnabled()) continue;
			
			Feature installed = null;
			for (Feature installedFeature : target.getInstalledFeatures()) {
				if (installedFeature.getId().equals(feature.getId())) {
					installed = installedFeature;
				}
			}
			
			if (installed == null || feature.isForce()) {
				//if (installed != null) {
				//	printInfo("Uninstalling feature " + installed.getId());
				//	uninstallFeature(installed, target);
				//}
				updateStatus("Installing feature " + feature.getId() + " in platform " + target.getName());
				runInstallFeatureCommand(MessageFormat.format(command, equinoxLauncher, feature.getId(), feature.getVersion(), product.getUpdatesite()));
			}
			else {
				updateStatus("Skipping " + feature.getId() + " in platform " + target.getName());
			}
		}
		
	}
	
	public void runInstallFeatureCommand(String command) throws Exception {
		
		Process process = Runtime.getRuntime().exec(command);
		String line;
		BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
		while ((line = input.readLine()) != null) {
			printInfo(line);
		}
		input.close();
		process.waitFor();
		
	}
	
	public String findEquinoxLauncher(Platform platform) {
		
		String location = platform.getLocation();
		if (!location.endsWith("/")) location += "/";
		
		location = location + "plugins/";
		File pluginsDir = new File(location);
		
		for (File plugin : pluginsDir.listFiles()) {
			if (plugin.getName().startsWith("org.eclipse.equinox.launcher_")) {
				return location + plugin.getName();
			}
		}
		
		return null;
	}
	
}
